#include "json_builder.h"
#include "frontend.h"
#include "parser.h"
#include "thirdparty/jsoncpp/include/json/value.h"
#include "thirdparty/jsoncpp/include/json/writer.h"

TypeOption CppTypeOptions = {
    {"seq", "std::vector"},
    {"dict", "std::unordered_map"},
    {"set", "std::unordered_set"},
    {"i8", "std::int8_t"},
    {"i16", "std::int16_t"},
    {"i32", "std::int32_t"},
    {"i64", "std::int64_t"},
    {"ui8", "std::uint8_t"},
    {"ui16", "std::uint16_t"},
    {"ui32", "std::uint32_t"},
    {"ui64", "std::uint64_t"},
    {"string", "std::string"},
    {"bool", "bool"},
    {"float", "float"},
    {"double", "double"},
    {"void", "void"},
    {"enum", "enum"},
};

TypeOption CSharpTypeOptions = {
    {"seq", "List"},  {"dict", "Dictionary"}, {"set", "HashSet"},
    {"i8", "sbyte"},  {"i16", "short"},       {"i32", "int"},
    {"i64", "long"},  {"ui8", "byte"},        {"ui16", "ushort"},
    {"ui32", "uint"}, {"ui64", "ulong"},      {"string", "string"},
    {"bool", "bool"}, {"float", "float"},     {"double", "double"},
    {"void", "void"}, {"enum", "enum"},
};

TypeOption ProtobufTypeOptions = {
    {"seq", "repeated"}, {"dict", "map"},    {"set", "repeated"},
    {"i8", "int32"},     {"i16", "int32"},   {"i32", "int32"},
    {"i64", "int64"},    {"ui8", "uint32"},  {"ui16", "uint32"},
    {"ui32", "uint32"},  {"ui64", "uint64"}, {"string", "string"},
    {"bool", "bool"},    {"float", "float"}, {"double", "double"},
    {"void", ""},        {"enum", "enum"},
};

TypeOption GolangTypeOptions = {
    {"seq", "[]"},      {"dict", "map"},      {"set", "map"},
    {"i8", "int8"},     {"i16", "int16"},     {"i32", "int32"},
    {"i64", "int64"},   {"ui8", "uint8"},     {"ui16", "uint16"},
    {"ui32", "uint32"}, {"ui64", "uint64"},   {"string", "string"},
    {"bool", "bool"},   {"float", "float32"}, {"double", "float64"},
    {"void", "void"},   {"enum", ""},
};

TypeOption IdlTypeOptions = {
    {"seq", "seq"},   {"dict", "dict"},   {"set", "set"},
    {"i8", "i8"},     {"i16", "i16"},     {"i32", "i32"},
    {"i64", "i64"},   {"ui8", "ui8"},     {"ui16", "ui16"},
    {"ui32", "ui32"}, {"ui64", "ui64"},   {"string", "string"},
    {"bool", "bool"}, {"float", "float"}, {"double", "double"},
    {"void", "void"}, {"enum", "enum"},
};

JsonBuilder::JsonBuilder(Frontend *frontend) : frontend_(frontend) {}

JsonBuilder::~JsonBuilder() {}

std::string getTypeString(ValueType type, const TypeOption &typeOptions) {
  switch (type) {
  case ValueType::I8:
    return typeOptions.at("i8");
  case ValueType::I16:
    return typeOptions.at("i16");
  case ValueType::I32:
    return typeOptions.at("i32");
  case ValueType::I64:
    return typeOptions.at("i64");
  case ValueType::UI8:
    return typeOptions.at("ui8");
  case ValueType::UI16:
    return typeOptions.at("ui16");
  case ValueType::UI32:
    return typeOptions.at("ui32");
  case ValueType::UI64:
    return typeOptions.at("ui64");
  case ValueType::BOOL:
    return typeOptions.at("bool");
  case ValueType::FLOAT:
    return typeOptions.at("float");
  case ValueType::DOUBLE:
    return typeOptions.at("double");
  case ValueType::STRING:
    return typeOptions.at("string");
  case ValueType::VOID:
    return typeOptions.at("void");
  case ValueType::ENUM:
    return typeOptions.at("enum");
  default:
    break;
  }
  return std::to_string((std::underlying_type<ValueType>::type)type);
}

std::string getIdlTypeString(ValueType type) {
  switch (type) {
  case ValueType::I8:
    return "i8";
  case ValueType::I16:
    return "i16";
  case ValueType::I32:
    return "i32";
  case ValueType::I64:
    return "i64";
  case ValueType::UI8:
    return "ui8";
  case ValueType::UI16:
    return "ui16";
  case ValueType::UI32:
    return "ui32";
  case ValueType::UI64:
    return "ui64";
  case ValueType::BOOL:
    return "bool";
  case ValueType::FLOAT:
    return "float";
  case ValueType::DOUBLE:
    return "double";
  case ValueType::STRING:
    return "string";
  case ValueType::VOID:
    return "void";
  case ValueType::ENUM:
    return "enum";
  default:
    break;
  }
  return std::to_string((std::underlying_type<ValueType>::type)type);
}

void buildNode(const IdlNode &node, Json::Value &field,
               const TypeOption &typeOptions, bool withName) {
  switch (node.type) {
  case ValueType::DICT: {
    field["type"] = typeOptions.at("dict");
    Json::Value jsonKey;
    jsonKey["IdlType"] = getIdlTypeString(node.first->type);
    if (node.first->type == ValueType::ENUM) {
      jsonKey["type"] = node.first->token.getText();
      jsonKey["isEnum"] = true;
    } else {
      jsonKey["type"] = getTypeString(node.first->type, typeOptions);
    }
    field["key"] = jsonKey;
    Json::Value jsonValue;
    if (node.second->type == ValueType::STRUCT) {
      jsonValue["isStruct"] = true;
      jsonValue["type"] = node.second->token.getText();
      jsonValue["IdlType"] = "struct";
    } else if (node.second->type == ValueType::ENUM) {
      jsonValue["isEnum"] = true;
      jsonValue["type"] = node.second->token.getText();
      jsonValue["IdlType"] = "enum";
    } else {
      jsonValue["type"] = getTypeString(node.second->type, typeOptions);
      jsonValue["IdlType"] = getIdlTypeString(node.second->type);
    }
    field["value"] = jsonValue;
    field["IdlType"] = "dict";
    if (withName) {
      field["name"] = node.token.getText();
    }
  } break;
  case ValueType::SEQ: {
    field["type"] = typeOptions.at("seq");
    Json::Value jsonKey;
    if (node.first->type == ValueType::STRUCT) {
      jsonKey["isStruct"] = true;
      jsonKey["type"] = node.first->token.getText();
      jsonKey["IdlType"] = "struct";
    } else if (node.first->type == ValueType::ENUM) {
      jsonKey["isEnum"] = true;
      jsonKey["type"] = node.first->token.getText();
      jsonKey["IdlType"] = "enum";
    } else {
      jsonKey["type"] = getTypeString(node.first->type, typeOptions);
      jsonKey["IdlType"] = getIdlTypeString(node.first->type);
    }
    field["key"] = jsonKey;
    field["IdlType"] = "seq";
    if (withName) {
      field["name"] = node.token.getText();
    }
  } break;
  case ValueType::SET: {
    field["type"] = typeOptions.at("set");
    Json::Value jsonKey;
    if (node.first->type == ValueType::STRUCT) {
      jsonKey["isStruct"] = true;
      jsonKey["type"] = node.first->token.getText();
    } else if (node.first->type == ValueType::ENUM) {
      jsonKey["isEnum"] = true;
      jsonKey["type"] = node.first->token.getText();
    } else {
      jsonKey["type"] = getTypeString(node.first->type, typeOptions);
    }
    jsonKey["IdlType"] = getIdlTypeString(node.first->type);
    field["key"] = jsonKey;
    field["IdlType"] = "set";
    if (withName) {
      field["name"] = node.token.getText();
    }
  } break;
  case ValueType::STRUCT: {
    field["isStruct"] = true;
    field["type"] = node.token.getText();
    field["IdlType"] = "struct";
    if (withName) {
      field["name"] = node.first->token.getText();
    }
  } break;
  case ValueType::ENUM: {
    field["isEnum"] = true;
    field["type"] = node.token.getText();
    field["IdlType"] = "enum";
    if (withName) {
      field["name"] = node.first->token.getText();
    }
  } break;
  default: {
    field["type"] = getTypeString(node.type, typeOptions);
    field["IdlType"] = getIdlTypeString(node.type);
    if (withName) {
      field["name"] = node.token.getText();
    }
    if (node.default_value) {
      field["default_value"] = *node.default_value;
    }
  } break;
  }
}

void buildServiceMethod(const IdlMethod &method, Json::Value &jsonMethod,
                        const TypeOption &typeOptions) {
  jsonMethod["name"] = method.name;
  Json::Value jsonRet;
  buildNode(method.retType, jsonRet, typeOptions, false);
  jsonMethod["retType"] = jsonRet;
  if (!method.oneway) {
    jsonMethod["timeout"] = method.timeout;
    if (method.retry) {
      jsonMethod["retry"] = method.retry;
    }
  }
  jsonMethod["is_stream"] = method.is_stream;
  jsonMethod["noexcept"] = method.no_except ? true : false;
  int index = 1;
  for (auto &arg : method.arg_list) {
    Json::Value jsonArg;
    jsonArg["index"] = index++;
    if (arg.name) {
      // Has argument name
      jsonArg["decl_name"] = *arg.name;
    }
    buildNode(arg, jsonArg, typeOptions, false);
    jsonMethod["arguments"].append(jsonArg);
  }
  if (method.arg_list.empty()) {
    Json::Value jsonArg;
    jsonArg["index"] = 1;
    jsonArg["type"] = getTypeString(ValueType::VOID, typeOptions);
    jsonArg["IdlType"] = getIdlTypeString(ValueType::VOID);
    jsonMethod["arguments"].append(jsonArg);
  }
}

bool JsonBuilder::build(const std::string &idlFile, const Parser &parser,
                        std::ostream &os, const TypeOption &typeOptions) {
  root_ = std::make_unique<Json::Value>();
  auto &root = *root_;
  Json::Value enumNames;
  // 添加idl file name
  root["idlname"] = idlFile;
  for (auto &enu : parser.getEnumList()) {
    enumNames.append(Json::Value(enu.name));
  }
  if (enumNames) {
    root["enumNames"] = enumNames;
  }
  Json::Value enums;
  for (auto &enu : parser.getEnumList()) {
    Json::Value enumValue;
    enumValue["name"] = enu.name;
    for (const auto &field : enu.enum_vec) {
      Json::Value enumField;
      enumField["name"] = field.name;
      enumField["value"] = field.value;
      enumValue["fields"].append(enumField);
    }
    enums.append(enumValue);
  }
  if (enums) {
    root["enums"] = enums;
  }
  Json::Value structNames;
  for (auto &stru : parser.getStructList()) {
    structNames.append(Json::Value(stru.name));
  }
  root["structNames"] = structNames;
  Json::Value serviceNames;
  for (auto &service : parser.getService()) {
    serviceNames.append(Json::Value(service.first));
  }
  root["serviceNames"] = serviceNames;
  Json::Value structs;
  for (auto &stru : parser.getStructList()) {
    Json::Value jsonStruct;
    jsonStruct["name"] = stru.name;
    int index = 1;
    for (auto &field : stru.field_list) {
      Json::Value jsonField;
      jsonField["index"] = index++;
      buildNode(field, jsonField, typeOptions, true);
      jsonStruct["fields"].append(jsonField);
    }
    structs.append(jsonStruct);
  }
  root["structs"] = structs;
  Json::Value services;
  for (auto &service : parser.getService()) {
    Json::Value jsonService;
    jsonService["name"] = service.first;
    if (service.second.loadType == ServiceLoadType::STATIC) {
      jsonService["loadType"] = "static";
    } else {
      jsonService["loadType"] = "dynamic";
    }
    if (service.second.type == ServiceType::MULTIPLE) {
      jsonService["type"] = "multiple";
      if (service.second.maxInst) {
        jsonService["maxInst"] = service.second.maxInst;
      }
    } else if (service.second.type == ServiceType::SINGLE) {
      jsonService["type"] = "single";
    } else if (service.second.type == ServiceType::REENTRANT) {
      jsonService["type"] = "reentrant";
    } else if (service.second.type == ServiceType::GENERIC) {
      jsonService["type"] = "generic";
    } else {
      jsonService["type"] = "single";
    }
    int index = 1;
    for (auto &method : service.second.method_list) {
      Json::Value jsonMethod;
      if (method.oneway) {
        jsonMethod["oneway"] = true;
      }
      if (method.event) {
        jsonMethod["event"] = true;
      }
      jsonMethod["index"] = index++;
      buildServiceMethod(method, jsonMethod, typeOptions);
      jsonService["methods"].append(jsonMethod);
    }
    if (service.second.method_list.empty()) {
      Json::Value value;
      value.resize(0);
      jsonService["methods"] = value;
    }
    for (auto &[name, notation] : service.second.service_notations) {
      Json::Value jsonNotation;
      for (auto &value : notation.values) {
        jsonNotation[name].append(value);
      }
      jsonService["notations"].append(jsonNotation);
    }
    jsonService["uuid"] = service.second.uuid;
    services.append(jsonService);
  }
  root["services"] = services;
  os << root.toStyledString();
  return true;
}

std::string JsonBuilder::get_string() {
  if (root_) {
    return root_->toStyledString();
  } else {
    return "";
  }
}
